package com.herotactics.fuentedatos;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import jugador.Jugador;
import logica.Partida;
import logica.Turno;
import mapa.Coordenada;
import mapa.Mapa;

import organizacion.Equipo;
import reflection.JarUtils;

import unidades.Unidad;
import unidades.UnidadHT;

/**
 * Clase que gestiona la base de datos faltan metodos
 * 
 * @author Ibai
 * 
 */
public class FuenteDatosJDBC implements FuenteDatos {

    /**
     * Atributo de clase que contiene la conexi�n. Es necesario hacer un init ()
     * para crearla.
     */
    private Connection connection = null;
    /**
     * Constate que contiene la ruta a la base de dato sqlite.
     */
    private static final String RUTA_BD = "jdbc:sqlite:Modulos/HeroTactics/FuenteDatos/DB/HeroTactics.s3db";
    /**
     * Constante que contiene el Class del driver a cargar dinamicamente.
     */
    private static final String DRIVER = "org.sqlite.JDBC";

    /**
     * Con el identificativo de la tupla de la tabla Equipo borra dicha tupla de
     * la base de datos.
     *
     * @param idEquipo
     * @return el n�mero de tuplas modificadas en la base de datos.
     * @throws Exception
     */
    private int deleteEquipo(int idEquipo) throws Exception {
        String sql = "DELETE FROM EQUIPO WHERE ID_EQUIPO = ?";
        int retorno = 0;
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1, idEquipo);
        retorno = preparedStatement.executeUpdate();
        return retorno;
    }

    /* (non-Javadoc)
     * @see com.herotactics.fuentedatos.FuenteDatos#deleteJugador(jugador.Jugador)
     */
    @Override
    public int deleteJugador(Jugador arg0) throws Exception {//FUNCIONA
        String sql = "DELETE FROM JUGADOR WHERE NOMBRE_JUGADOR = ?";
        int retorno = 0;
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1, arg0.getNombre());
        retorno = preparedStatement.executeUpdate();
        return retorno;
    }

    /* (non-Javadoc)
     * @see com.herotactics.fuentedatos.FuenteDatos#deletePartida(logica.Partida)
     */
    @Override
    public int deletePartida(Partida arg0) throws Exception {
        String sql = "DELETE FROM PARTIDA WHERE FECHA = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setLong(1, arg0.getFecha());
        return preparedStatement.executeUpdate();
    }

    /* (non-Javadoc)
     * @see com.herotactics.fuentedatos.FuenteDatos#deleteUnidad(unidades.UnidadHT)
     */
    @Override
    public int deleteUnidad(Unidad arg0) throws Exception { //FUNCIONA
        UnidadHT arg = (UnidadHT) arg0;
        String sql = "DELETE FROM UNIDAD WHERE ID_UNIDAD = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1, arg.getId());
        return preparedStatement.executeUpdate();

    }

    /* (non-Javadoc)
     * @see com.herotactics.fuentedatos.FuenteDatos#end()
     */
    @Override
    public void end() throws Exception { //FUNCIONA
        connection.close();
    }

    /**
     *
     * @param fecha
     * @return Devuelve el objeto Partida con la fecha que se le a pasado por
     *         parametro.
     * @throws Exception
     */
    public Partida getPartida(long fecha) throws Exception {
        String sql = "SELECT * FROM PARTIDA WHERE FECHA = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setLong(1, fecha);
        ResultSet rs = preparedStatement.executeQuery();
        rs.next();
        Jugador jugadorA = getJugador(rs.getInt("EQUIPOA"));
        Jugador jugadorB = getJugador(rs.getInt("EQUIPOB"));
        Turno turno = getTurno(rs.getInt("TURNO"), rs.getInt("PUNTOS_ACCION"));
        Mapa mapa = getMapa(rs.getInt("ID_MAPA"));
        Partida partida = new Partida(mapa, turno, jugadorA, jugadorB, fecha);
        partida.getTurno().setPartida(partida);
        partida.setPosicionado();
        //return new Partida(mapa, turno, jugadorA, jugadorB, fecha, new FuenteDatosJDBC());
        return partida;
    }

    /* (non-Javadoc)
     * @see com.herotactics.fuentedatos.FuenteDatos#getAllPartida()
     */
    @Override
    public Collection<Partida> getAllPartida() throws Exception {
        Collection<Partida> collection = new ArrayList<Partida>();
        String sql = "SELECT FECHA FROM PARTIDA";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        ResultSet rs = preparedStatement.executeQuery();
        while (rs.next()) {
            Partida p = getPartida(rs.getLong("FECHA"));
            p.setPosicionado();
            collection.add(p);
        }
        return collection;
    }

    /**
     * @param idEquipo
     * @return Devuelve el objeto Equipo con la idEquipo que se le a pasado por
     *         parametro.
     * @throws Exception
     */
    private Equipo getEquipo(int idEquipo) throws Exception {
        ArrayList<Unidad> unidades = new ArrayList<Unidad>();
        String sql = "SELECT * FROM EQUIPO_HAS_UNIDAD WHERE ID_EQUIPO =?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1, idEquipo);
        ResultSet rs = preparedStatement.executeQuery();
        while (rs.next()) {
            UnidadHT unidad = getUnidad(rs.getInt("ID_UNIDAD"));
            unidad.setVidaPartida(rs.getInt("VIDA_PARTIDA"));
            unidad.setPosicion(new Coordenada(rs.getInt("POS_X"), rs.getInt("POS_Y")));
            unidad.setPuedeAtacar(rs.getBoolean("PUEDE_ATACAR"));
            unidad.setPuedeMover(rs.getBoolean("PUEDE_MOVER"));
            unidades.add(unidad);
        }
        return new Equipo(unidades);
    }

    /**
     * @param idEquipo
     * @return Devuelve el objeto Jugador con la idEquipo que se le a pasado por
     *         parametro.
     * @throws Exception
     */
    public Jugador getJugador(int idEquipo) throws Exception {
        String sql1 = "SELECT NOMBRE_JUGADOR FROM EQUIPO WHERE ID_EQUIPO = ?;";
        String sql2 = "SELECT CREDITOS FROM JUGADOR WHERE NOMBRE_JUGADOR = ?;";
        String nombreJugador = null;
        int creditos = 0;
        PreparedStatement preparedStatement1 = connection.prepareStatement(sql1);
        preparedStatement1.setInt(1, idEquipo);
        ResultSet rs1 = preparedStatement1.executeQuery();
        while (rs1.next()) {
            nombreJugador = rs1.getString("NOMBRE_JUGADOR");
        }
        preparedStatement1.close();
        PreparedStatement preparedStatement2 = connection.prepareStatement(sql2);
        preparedStatement2.setString(1, nombreJugador);
        ResultSet rs2 = preparedStatement2.executeQuery();
        while (rs2.next()) {
            creditos = rs2.getInt("CREDITOS");
        }
        Jugador jugador = new Jugador(nombreJugador, creditos);
        jugador.setEquipo(getEquipo(idEquipo));
        return jugador;
    }

    /* (non-Javadoc)
     * @see com.herotactics.fuentedatos.FuenteDatos#getAllMapa()
     */
    @Override
    public Collection<Mapa> getAllMapa() throws IOException, ClassNotFoundException {//FUNCIONA

        File[] files = new File("").listFiles();//TODO
        Collection<Mapa> collection = new ArrayList<Mapa>();
        for (File f : files) {
            FileInputStream fMapa = new FileInputStream(f);
            ObjectInputStream mapa = new ObjectInputStream(fMapa);
            collection.add((Mapa) mapa.readObject());
            mapa.close();
            fMapa.close();
        }
        return collection;
    }


    /* (non-Javadoc)
     * @see com.herotactics.fuentedatos.FuenteDatos#getMapa(int)
     */
    @Override
    public Mapa getMapa(int idMapa) throws IOException, ClassNotFoundException {
//        FileInputStream fMapa = new FileInputStream("C:/Users/Gonzalo/Documents/ProgramacionIV/mapas/mapa1" + ".map");//TODO
//        ObjectInputStream mapa = new ObjectInputStream(fMapa);
//        Mapa m = ((Mapa) mapa.readObject());
//        mapa.close();
//        fMapa.close();
//        return m;
        Collection<String> mapas = Mapa.getMapas();
        for (String s : mapas) {
            Mapa mapa = Mapa.getMapa(s);
            if (mapa.getIdMapa() == idMapa) {
                return mapa;
            }
        }
        throw new IllegalArgumentException("El mapa no existe");
    }

    /**
     * @param idEquipo
     * @param puntosAccion
     * @return Devuelve el objeto Turno con la idEquipo que se le a pasado por
     *         parametro y actualiza los puntos de acci�n con el segundo
     *         parametro.
     * @throws Exception
     */
    private Turno getTurno(int idEquipo, int puntosAccion) throws Exception {
        return new Turno(getJugador(idEquipo), null, puntosAccion);
    }

    /**
     * @param idUnidad
     * @return Devuelve el objeto UnidadHT con la idUnidad que se le a pasado por parametro.
     * @throws Exception
     */
    public UnidadHT getUnidad(int idUnidad) throws Exception {//FUNCIONA
        String path = null;
        String sql = "SELECT PATH_UNIDAD FROM UNIDAD WHERE ID_UNIDAD =?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1, idUnidad);
        ResultSet rs = preparedStatement.executeQuery();
        while (rs.next()) {
            path = rs.getString("PATH_UNIDAD");
        }
        if (path != null && !path.trim().isEmpty()) {
            Class<?> clase = Class.forName(path, false, JarUtils.getClassLoader());
            Object objeto = clase.newInstance();
            if (objeto instanceof UnidadHT) {
                return (UnidadHT) objeto;
            }
        }
        return null;
    }

    /* (non-Javadoc)
     * @see com.herotactics.fuentedatos.FuenteDatos#init()
     */
    @Override
    public void init() throws Exception {//FUNCIONA
        Class.forName(DRIVER);
        connection = DriverManager.getConnection(RUTA_BD);
    }

    /**
     * @param arg0
     * @param jugador
     * @return el id_equipo
     * @throws Exception
     */
    private synchronized int insertEquipo(Equipo arg0, String jugador) throws Exception {

        {
            String sql = "INSERT INTO EQUIPO (NOMBRE_JUGADOR) VALUES (?)";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, jugador);
            preparedStatement.executeUpdate();
        }
        String sql = "SELECT MAX(ID_EQUIPO) FROM EQUIPO";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        ResultSet rs = preparedStatement.executeQuery();
        rs.next();
        int idEquipo = rs.getInt(1);
        insertUnidad(arg0.getUnidades());
        insertEquipoHasUnidad(arg0.getUnidades(), idEquipo);
        return idEquipo;
    }

    /**
     * Llamado por insertEquipo inserta todas las unidades dependiendo del equipo en la tabla EQUIPO_HAS_UNIDAD
     * @param arg0
     * @param idEquipo
     * @return tuplas modificadas
     * @throws Exception
     */
    private synchronized int insertEquipoHasUnidad(Collection<Unidad> arg0, int idEquipo) throws Exception {
        int tuplasModificadas = 0;
        Iterator<Unidad> iterator = arg0.iterator();
        while (iterator.hasNext()) {
            UnidadHT unidad = (UnidadHT) iterator.next();
            String sql = "INSERT INTO EQUIPO_HAS_UNIDAD (ID_EQUIPO,ID_UNIDAD,VIDA_PARTIDA,POS_X,POS_Y,PUEDE_ATACAR,PUEDE_MOVER,PUEDE_REGENERAR) VALUES (?,?,?,?,?,?,?,?)";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, idEquipo);
            preparedStatement.setInt(2, unidad.getId());
            preparedStatement.setInt(3, unidad.getVidaPartida());
            preparedStatement.setInt(4, unidad.getPosicion().getX());
            preparedStatement.setInt(5, unidad.getPosicion().getY());
            preparedStatement.setBoolean(6, unidad.puedeAtacar());
            preparedStatement.setBoolean(7, unidad.puedeMover());
            preparedStatement.setBoolean(8, unidad.puedeRegenerar());
            tuplasModificadas += preparedStatement.executeUpdate();
        }
        return tuplasModificadas;
    }

    /* (non-Javadoc)
     * @see com.herotactics.fuentedatos.FuenteDatos#insertJugador(jugador.Jugador)
     */
    @Override
    public int insertJugador(Jugador arg0) throws Exception {//FUNCIONA
        String sql = "SELECT * FROM JUGADOR WHERE NOMBRE_JUGADOR=?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1, arg0.getNombre());
        ResultSet rs = preparedStatement.executeQuery();
        if (!rs.next()) {
            sql = "INSERT INTO JUGADOR (NOMBRE_JUGADOR,CREDITOS) VALUES (?,?)";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, arg0.getNombre());
            preparedStatement.setInt(2, arg0.getCreditos());
            return preparedStatement.executeUpdate();
        } else {
            return 0;
        }
    }

    /* (non-Javadoc)
     * @see com.herotactics.fuentedatos.FuenteDatos#insertPartida(logica.Partida)
     */
    @Override
    public int insertPartida(Partida arg0) throws Exception {
        String sql = "INSERT INTO PARTIDA (FECHA,EQUIPOA,EQUIPOB,TURNO,ID_MAPA,PUNTOS_ACCION) VALUES (?,?,?,?,?,?)";
        int equipoA = insertEquipo(arg0.getJugadorA().getEquipo(), arg0.getJugadorA().getNombre());
        int equipoB = insertEquipo(arg0.getJugadorB().getEquipo(), arg0.getJugadorB().getNombre());
        int turno = 0;
        updateJugador(arg0.getJugadorA());
        updateJugador(arg0.getJugadorB());
        if (arg0.getTurno().getJugadorActivo().getNombre().equals(arg0.getJugadorA().getNombre())) {
            turno = equipoA;
        } else {
            turno = equipoB;
        }
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setLong(1, System.currentTimeMillis());
        preparedStatement.setInt(2, equipoA);
        preparedStatement.setInt(3, equipoB);
        preparedStatement.setInt(4, turno);
        preparedStatement.setInt(5, arg0.getMapa().getIdMapa());
        preparedStatement.setInt(6, arg0.getTurno().getPuntosAccion());

        return preparedStatement.executeUpdate();
    }

    /**
     * @param arg0
     * @return el numero de tuplas insertadas en la tabla Unidad.
     * @throws Exception
     */
    public int insertUnidad(UnidadHT arg0) throws Exception { //FUNCIONA
        UnidadHT unidad = getUnidad(arg0.getId());
        if (unidad == null) {
            String sql = "INSERT INTO UNIDAD (ID_UNIDAD,PATH_UNIDAD) VALUES (?,?)";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, arg0.getId());
            preparedStatement.setString(2, arg0.getClass().getName());
            return preparedStatement.executeUpdate();
        } else {
            return 0;
        }
    }


    /* (non-Javadoc)
     * @see com.herotactics.fuentedatos.FuenteDatos#insertUnidad(java.util.Collection)
     */
    @Override
    public int insertUnidad(Collection<Unidad> arg0) throws Exception {
        int total = 0;
        for (Unidad u : arg0) {
            total += insertUnidad((UnidadHT) u);
        }
        return total;
    }

    /* (non-Javadoc)
     * @see com.herotactics.fuentedatos.FuenteDatos#updateJugador(jugador.Jugador)
     */
    @Override
    public int updateJugador(Jugador jugador) throws Exception { //FUNCIONA
        String sql = "UPDATE JUGADOR SET CREDITOS = ? WHERE NOMBRE_JUGADOR= ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1, jugador.getCreditos());
        preparedStatement.setString(2, jugador.getNombre());
        return preparedStatement.executeUpdate();

    }

    @Override
    public Collection<String> getAllJugadores() throws SQLException {
        Collection<String> collection = new ArrayList<String>();
        String sql = "SELECT NOMBRE_JUGADOR FROM JUGADOR";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        ResultSet rs = preparedStatement.executeQuery();
        while (rs.next()) {
            collection.add(rs.getString(1));
        }
        return collection;
    }
}
